package com.ns.school.common.utils;

import jakarta.servlet.http.HttpServletRequest;

/**
 * ip工具类
 *
 * @author Spark
 */
public class IPUtil {

    private static final String UNKNOWN = "unknown";
    private static final String REMOTE_IP = "remoteip";
    private static final String X_FORWARDED_FOR = "x-forwarded-for";
    private static final String PROXY_CLIENT_IP = "proxy-client-ip";
    private static final String WL_PROXY_CLIENT_IP = "wl-proxy-client-ip";
    private static final String X_REAL_IP = "x-real-ip";

    /**
     * ip地址转成long型数字 将IP地址转化成整数的方法如下： 1、通过String的split方法按.分隔得到4个长度的数组
     * 2、通过左移位操作（<<）给每一段的数字加权，第一段的权为2的24次方，第二段的权为2的16次方，第三段的权为2的8次方，最后一段的权为1
     *
     * @param strIp
     * @return
     */
    public static long ipToLong(String strIp) {
        if (StrUtil.isEmpty(strIp)) {
            return 0;
        }
        if (!strIp.contains(".")) {
            return 0;
        }
        String[] ip = strIp.split("\\.");
        return (Long.parseLong(ip[0]) << 24) + (Long.parseLong(ip[1]) << 16) + (Long.parseLong(ip[2]) << 8) + Long.parseLong(ip[3]);
    }

    /**
     * 获取数字类型的ip地址
     *
     * @param request
     * @return
     */
    public static long ipToLong(HttpServletRequest request) {
        return ipToLong(getRemoteIP(request));
    }

    /**
     * 将十进制整数形式转换成127.0.0.1形式的ip地址 将整数形式的IP地址转化成字符串的方法如下：
     * 1、将整数值进行右移位操作（>>>），右移24位，右移时高位补0，得到的数字即为第一段IP。
     * 2、通过与操作符（&）将整数值的高8位设为0，再右移16位，得到的数字即为第二段IP。
     * 3、通过与操作符吧整数值的高16位设为0，再右移8位，得到的数字即为第三段IP。 4、通过与操作符吧整数值的高24位设为0，得到的数字即为第四段IP。
     *
     * @param longIp
     * @return
     */
    public static String longToIP(long longIp) {
        // 直接右移24位
        return (longIp >>> 24) +
                "." +
                // 将高8位置0，然后右移16位
                ((longIp & 0x00FFFFFF) >>> 16) +
                "." +
                // 将高16位置0，然后右移8位
                ((longIp & 0x0000FFFF) >>> 8) +
                "." +
                // 将高24位置0
                (longIp & 0x000000FF);
    }

    /**
     * 获取请求者IP地址
     *
     * @param request
     * @return
     */
    public static long getRemoteIPLong(HttpServletRequest request) {
        return ipToLong(request);
    }

    /**
     * 获取请求者IP地址
     *
     * @param request
     * @return
     */
    public static String getRemoteIP(HttpServletRequest request) {
        if (request == null) {
            return null;
        }
        // tomcat会将x-forwarded-for转成remoteip
        String ip = applyIp(request.getHeader(REMOTE_IP));
        if (isNull(ip)) {
            ip = applyIp(request.getHeader(X_FORWARDED_FOR));
        }
        if (isNull(ip)) {
            ip = applyIp(request.getHeader(PROXY_CLIENT_IP));
        }
        if (isNull(ip)) {
            ip = applyIp(request.getHeader(WL_PROXY_CLIENT_IP));
        }
        if (isNull(ip)) {
            ip = applyIp(request.getHeader(X_REAL_IP));
        }
        if (isNull(ip)) {
            ip = applyIp(request.getRemoteAddr());
        }
        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
    }

    private static String applyIp(String ip) {
        if (isNull(ip)) {
            return ip;
        }
        return !ip.contains(",") ? ip : ip.split(",")[0];
    }

    private static boolean isNull(String ip) {
        return StrUtil.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip);
    }

    /**
     * 从多级反向代理中获得第一个非unknown IP地址
     *
     * @param ip 获得的IP地址
     * @return 第一个非unknown IP地址
     */
    public static String getMultistageReverseProxyIp(String ip) {
        // 多级反向代理检测
        if (ip != null && ip.indexOf(",") > 0) {
            final String[] ips = ip.trim().split(",");
            for (String subIp : ips) {
                if (!isUnknown(subIp)) {
                    ip = subIp;
                    break;
                }
            }
        }
        return StrUtil.substring(ip, 0, 255);
    }

    /**
     * 检测给定字符串是否为未知，多用于检测HTTP请求相关
     *
     * @param checkString 被检测的字符串
     * @return 是否未知
     */
    public static boolean isUnknown(String checkString) {
        return StrUtil.isEmpty(checkString) || UNKNOWN.equalsIgnoreCase(checkString);
    }
}
